package LRES;

import java.util.List;

/**
 * 单结点输出BP神经网络，计算担保风险系数
 * keng die
 * 搞的头都大了……终于明白了=。=
 * 可能是这样的：先把所有样本训练一遍，然后再计算训练一遍后的样本误差(也就是说对于单个样本，不需要进行总体误差计算)。
 * 如果误差不在范围内，训练次数+1，然后如此反复，直到误差在范围内或者超过训练次数。
 */

public class CopyOfOneOut {
	private Coefficient cf;//参数
	private int inputNum = cf.getInputNum(); // 输入层神经元数目
	private int hideNum = cf.getHideNum(); // 隐层神经元数目，前馈网络隐藏层
	private int outputNum = cf.getOutputNum(); // 输出层神经元数模，各个命令的开关阀
	private double[][] hideWeight = cf.getHideWeight(); // 输入层至隐层权值，输入层或者隐层的行列位置无所谓。
	private double[][] outWeight = cf.getOutWeight(); // 隐层至输出层权值，这个矩阵的行列顺序需要和上面的对应
	//	private double HideWeightTemp[][] = new double[hideNum][inputNum]; // 保存前一组输入层到隐层的权值
	//	private double OutWeightTemp[][] = new double[outputNum][hideNum]; // 保存前一组隐层到输出层的权值
	private double[] hideThreshold = cf.getHideThreshold(); // 隐层的阈值
	private double[] outputThreshold = cf.getOutputThreshold(); // 输出层的阈值
	private double[] hideIn = cf.getHideIn(); // 隐层的输入,各个隐藏层的神经单元的内积,Sigmod函数所处理的参数
	private double[] outputIn = cf.getOutputIn(); // 输出层的输入，各个输出层的神经单元的内积，Sigmod函数所处理的参数
	private double[] hide = cf.getHide(); // 隐层的输出，s型函数之后的输出值
	private double[] output = cf.getOutput(); // 输出层的输出，s型函数之后的输出值
	private double alpha = cf.getAlpha(); // 动量因子，改进型bp算法使用
	private double error = cf.getError(); // 第m个样本的整体误差
	private double a = cf.getA(); // 输出层至隐层学习效率，反向传播，误差分析时用到
	private double b = cf.getB(); // 隐层至输入层学习效率，反向传播，误差分析时用到
	private double[] outputErr = cf.getOutputErr(); // δk 隐藏层到输出层的误差，一般化误差
	private double[] hideErr = cf.getHideErr(); // δj 输入层到隐藏层的误差，一般化误差
	private double[][] oldHide = cf.getOldHide(); // 新旧权值更新量交替--改进型Bp算法
	private double[][] oldOutput = cf.getOldOutput();
	private double Exceptation; // 期望值
	private double[] traininput; // 训练输入数组
	private double[] input=cf.getInput();
	private double precision=cf.getPrecision();  //精度
	private int counter=cf.getCounter(); //当前训练次数
	private int times=cf.getTimes();  //训练次数边界


	/**
	 * 初始化权值、阈值 由于Sigmoid函数的特性，初始权值分布在-0.5~0.5
	 */
	private void initial() {
		/* 初始化输入层到隐层的权值，随机模拟0.5~-0.5 */
		for (int i = 0; i < hideNum; i++) {
			for (int q = 0; q < inputNum; q++) {
				hideWeight[i][q] = (double) (Math.random() - 0.5);
			}
		}

		/* 初始化隐层到输出层的权值，随机模拟0.5~-0.5 */
		for (int i = 0; i < outputNum; i++) {
			for (int j = 0; j < hideNum; j++) {
				outWeight[i][j] = (double) (Math.random() - 0.5);
			}
		}

		/* 隐层阈值初始化 */
		for (int i = 0; i < hideNum; i++) {
			hideThreshold[i] = (double) (Math.random() - 0.5);
		}

		/* 输出层阈值初始化 */
		for (int i = 0; i < outputNum; i++) {
			outputThreshold[i] = (double) (Math.random() - 0.5);
		}
	}

	/**
	 * 求净输入，输出
	 * @param input 输入
	 */
	private void computeInOut(double[] input) {
		double sigma1, sigma2;

		/* 求隐层内积HideIn和输出Hide */
		for (int i = 0; i < hideNum; i++) {
			sigma1 = 0;

			for (int j = 0; j < inputNum; j++)
				sigma1 = sigma1 + hideWeight[i][j] * input[j]; // 对于第i个隐层单元，sigma1=第i层所有{输入值*对应的权值}的和

			hideIn[i] = sigma1 - hideThreshold[i]; // 权值*输入值 总和 再减去阈值（源程序为加上阈值，这里做了修改）
			hide[i] = (1 / (1 + Math.exp(-hideIn[i]))); // 隐层内积的s型函数输出 Math.exp(double x)
		}

		/* 求输出层内积OutputIn和输出Output */
		for (int i = 0; i < outputNum; i++) {
			sigma2 = 0;
			for (int j = 0; j < hideNum; j++)
				sigma2 = sigma2 + outWeight[i][j] * hide[j];

			outputIn[i] = sigma2 - outputThreshold[i];
			output[i] = (1 / (1 + Math.exp(-outputIn[i]))); // 输出层内积的s型函数输出Math.exp(double x)
		}
	}

	/**
	 * 误差分析
	 * 输出层的误差 δk
	 */
	private void errOutHide() {
		double abs_err[] = new double[outputNum];
		double sqr_err = 0.0f; 
		for (int i = 0; i < outputNum; i++) {
			abs_err[i] = Exceptation - output[i]; // 误差信号 e(n)=d(n)-y(n)
			sqr_err = sqr_err + abs_err[i] * abs_err[i];
			outputErr[i] = abs_err[i] * output[i] * (1 - output[i]); // 输出层一般化误差：期望值与输出值的差*输出值*(1-输出值)
		}
		sqr_err=sqr_err/2;//一个样本的误差
		error+=sqr_err; // 整体误差。
	}

	/**
	 * 误差分析
	 * 隐藏层的误差 δj
	 */
	private void errHideIn() {
		double sigma;
		for (int i = 0; i < hideNum; i++) {
			sigma = 0;
			for (int j = 0; j < outputNum; j++)
				sigma = sigma + outputErr[j] * outWeight[j][i]; // 这里还是存在某些疑问，pdf中第76页最上面的那个公式中后面求和是求的谁的和？

			hideErr[i] = sigma * hide[i] * (1 - output[i]); // 这里修改了源程序，Hide[i]->Output[i]
		}
	}

	/**
	 * 总误差 各样本w误差之和 当需要批量训练的时候才用
	 */
	//	private double errorSum() {
	//		double total_err = 0;
	//
	//		for (Double a:error)
	//			total_err += a;
	//
	//		return total_err;
	//	}

	/**
	 * 新旧权值更新量交替--改进型Bp算法
	 */
	private void saveWV() {
		// 输入层到隐层的权值更新
		for (int i = 0; i < hideNum; i++) {
			for (int j = 0; j < inputNum; j++)
				oldHide[i][j] = b * hideErr[i] * traininput[j];
		}
		// 隐层到输出层的权值更新
		for (int i = 0; i < outputNum; i++) {
			for (int j = 0; j < hideNum; j++)
				oldOutput[i][j] = a * outputErr[i] * hide[j];
		}
	}

	/**
	 * 更新权值，阈值 输出层更新
	 */
	private void updateOutHide() {
		for (int i = 0; i < outputNum; i++) {
			for (int j = 0; j < hideNum; j++)
				// V_Hide_Out[i][j]=V_Hide_Out[i][j]+a*d_err[i]*H[j];
				// V_Hide_Out[i][j]=V_Hide_Out[i][j]+(1.0-alpha)*a*d_err[i]*H[j]+alpha*a*d_err[i]*H[j];
				outWeight[i][j] = outWeight[i][j] + (1 - alpha) * a
				* outputErr[i] * hide[j] + alpha * oldOutput[i][j];// 这里有分歧，参看基于人工神经网络的商业银行贷款风险预警研究
			outputThreshold[i] = outputThreshold[i] + a * outputErr[i];
		}
	}

	/**
	 * 更新权值，阈值 隐层更新
	 */
	private void updateHideIn() {

		for (int i = 0; i < hideNum; i++) {
			for (int j = 0; j < inputNum; j++)
				// Weight_In_Hide[j][i]=Weight_In_Hide[j][i]+b*e_err[j]*input[i];
				// Weight_In_Hide[j][i]=Weight_In_Hide[j][i]+(1.0-alpha)*b*e_err[j]*input[i]+alpha*b*e_err[j]*input[i];
				hideWeight[i][j] = hideWeight[i][j] + (1 - alpha) * b
				* hideErr[i] * traininput[j] + alpha * oldHide[i][j];
			hideThreshold[i] = hideThreshold[i] + b * hideErr[i];
		}
	}

	private void clear(){
		error=0;
	}

	/**
	 * 训练BP神经网络
	 */
	public void trainBP(Coefficient cf){
		List<double[]> sample=cf.getSample();//样本数据
		initial();//初始化权值阈值
		while(counter<times){
			for(double[] s:sample){
				System.arraycopy(s, 0, traininput, 0, s.length-2);
				Exceptation=s[s.length-1];
				computeInOut(traininput);
				errOutHide();
				errHideIn();
				saveWV();
				updateOutHide();
				updateHideIn();
			}
			if(error<=precision){
				cf.setError(error);
				break;
			}
			else{
				counter++;
				clear();
			}
		}
		cf.setCounter(counter);
	}

	/**
	 * 运行神经网络
	 */
	public void runBP(){
		computeInOut(input);
	}
}